package com.zt.questionnaire.service;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.WxMaUserService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import cn.binarywang.wx.miniapp.util.WxMaConfigHolder;
import com.nimbusds.jose.JOSEException;
import com.zt.questionnaire.common.HService;
import com.zt.questionnaire.common.PageResult;
import com.zt.questionnaire.common.config.redis.CacheOperator;
import com.zt.questionnaire.common.msg.HErrorCode;
import com.zt.questionnaire.common.utils.AssertUtils;
import com.zt.questionnaire.common.utils.ObjectUtils;
import com.zt.questionnaire.common.utils.TimeUtils;
import com.zt.questionnaire.common.utils.jwt.TokenUtils;
import com.zt.questionnaire.db.entity.UserEntity;
import com.zt.questionnaire.db.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 用户信息表
 *
 * @author funnywus
 * @email funnywus@163.com
 * @date 2023-09-01 11:13:06
 */
@Slf4j
@Service
public class UserService {

    @Autowired
    private UserMapper mapper;
    @Autowired
    private CacheOperator redis;
    @Autowired
    private WxMaService wxMaService;

    /**
     * 过期时间
     **/
    public long timeOut = TimeUtils.DAY;

    private static final String KEY = "user@%d";
    // 用户openId对应着用户id
    private static final String USER_OPEN_ID_KEY = "user_open_id@%s";
    /**
     * 通过id获取token,目的：实现顶号登录，参数：账号id，登录来源（加来源为了实现app和公众号同时登录）
     */
    private static final String USER_TOKEN_BY_ID_KEY = "user_token_by_id@%d";
    /**
     * token过期时间,暂定设置过期为7个月
     */
    public static long TOKEN_EXPIRE_TIME = 3600 * 24 * 7;  //单位秒

    /**
     * 组装主键key
     *
     * @return
     */
    private static String formatKey(Long userId) {
        return String.format(KEY, userId);
    }

    public static String formatTokenByIdKey(long id) {
        return String.format(USER_TOKEN_BY_ID_KEY, id);
    }

    /**
     * 分页获取
     *
     * @param page 第几页
     * @param num  每页显示几条
     * @return
     */
    public PageResult<UserEntity> getPage(Integer page, Integer num) {
        Example example = new Example(UserEntity.class);
        Example.Criteria criteria = example.createCriteria();
        return HService.getListAndNum(mapper, example, page, num);
    }

    public List<UserEntity> getList() {
        Example example = new Example(UserEntity.class);
        Example.Criteria criteria = example.createCriteria();
        return mapper.selectByExample(example);
    }

    /**
     * 根据主键获取
     *
     * @param userId
     * @return
     */
    public UserEntity get(Long userId) {
        UserEntity obj = getRedis(userId);
        if (obj != null) {
            return obj;
        }
        obj = getDb(userId);
        if (obj != null) {
            setRedis(obj);
        }
        return obj;
    }

    public UserEntity getDb(Long userId) {
        UserEntity obj = new UserEntity();
        obj.setUserId(userId);
        return mapper.selectByPrimaryKey(obj);
    }

    /**
     * 保存
     *
     * @param obj
     * @return
     */
    public boolean save(UserEntity obj) {
        boolean is = saveDb(obj);
        if (!is) {
            return false;
        }
        setRedis(obj);
        return true;
    }

    public boolean saveDb(UserEntity obj) {
        return mapper.insert(obj) > 0;
    }

    /**
     * 更新
     *
     * @param obj
     * @return
     */
    public boolean update(UserEntity obj) {
        boolean is = updateDb(obj);
        if (!is) {
            return false;
        }
        setRedis(obj);
        return true;
    }

    public boolean updateDb(UserEntity obj) {
        return mapper.updateByPrimaryKey(obj) > 0;
    }

    /**
     * 根据主键删除
     *
     * @param userId
     * @return
     */
    public boolean remove(Long userId) {
        boolean is = removeDb(userId);
        if (!is) {
            return false;
        }
        removeRedis(userId);
        return true;
    }

    public boolean removeDb(Long userId) {
        UserEntity obj = new UserEntity();
        obj.setUserId(userId);
        return mapper.deleteByPrimaryKey(obj) > 0;
    }

    private UserEntity getRedis(Long userId) {
        String formatKey = formatKey(userId);
        return redis.getObject(formatKey, UserEntity.class, timeOut);
    }

    private void removeRedis(Long userId) {
        String formatKey = formatKey(userId);
        redis.remove(formatKey);
    }

    private void setRedis(UserEntity obj) {
        String formatKey = formatKey(obj.getUserId());
        redis.setObject(formatKey, obj, timeOut);
    }

    /**
     * @param openid
     * @return
     */
    public UserEntity getByOpenId(String openid) {
        UserEntity obj = new UserEntity();
        obj.setOpenId(openid);
        List<UserEntity> list = mapper.select(obj);
        if (ObjectUtils.isEmpty(list)) {
            return null;
        }
        return list.get(0);
    }

    /**
     * 用户登录，并且返回前端需要的openId等信息
     *
     * @param code
     * @return
     */
    public UserEntity login(String code) {
        WxMaJscode2SessionResult sessionInfo;
        try {
            WxMaUserService wxMaUserService = wxMaService.getUserService();
            if (wxMaUserService == null) {
                log.error("微信授权失败 code1={}", code);
                AssertUtils.newThrow("微信授权失败");
            }
            sessionInfo = wxMaUserService.getSessionInfo(code);
            if (sessionInfo == null) {
                log.error("微信授权失败 code2={}", code);
                AssertUtils.newThrow("微信授权失败2");
            }
            String openid = sessionInfo.getOpenid();
            String unionid = sessionInfo.getUnionid();
            String sessionKey = sessionInfo.getSessionKey();
            UserEntity user = getByOpenId(openid);
            // 1.查询用户是否存在
            //   存在，设置用户登录时间信息， 返回前端所需信息
            //   不存在,创建用户并返回， 返回前端所需信息
            long currTime = TimeUtils.getCurrTime();
            if (user != null) {
                user.setLatestLoginTime(currTime);
                user.setSessionKey(sessionKey);
                boolean is = update(user);
                AssertUtils.isFalse(is, "更新用户失败");
            } else {
                user = new UserEntity();
                user.setUnionId(unionid);
                user.setOpenId(openid);
                user.setRegisterTime(currTime);
                user.setLatestLoginTime(currTime);
                user.setSessionKey(sessionKey);
                boolean is = save(user);
                AssertUtils.isFalse(is, "保存用户失败");
            }
            String token = getToken(user);
            user.setToken(token);
            return user;
        } catch (Exception e) {
            log.error("微信授权报错 code={}", code);
            log.error(e.getMessage(), e);
            AssertUtils.newThrow("微信授权失败");
        } finally {
            WxMaConfigHolder.remove();//清理ThreadLocal
        }
        return null;
    }

    /**
     * 获取用户token
     *
     * @param user
     * @return
     */
    private String getToken(UserEntity user) {
        Long userId = user.getUserId();
        String oldToken = getTokenByIdRedis(userId);
        if (oldToken != null && !"".equals(oldToken)) {
            removeTokenByIdRedis(userId);
            // TokenUtils生成token的时候存入到了缓存，这里要删除，实现每次登录更换token，并删除上次的token
            redis.remove(oldToken);
            // accountService.removeIdByTokenRedis(oldToken);
        }
        // 存放用户基本信息，每个服务不用每次来拿基本信息，当这些信息不够用的时候再来拿，过期时间存在payloadMap里面也可以，
        // 但是目前是在创建token的时候存在redis里面的，因为要考虑顶号登录的问题，所以存redis里面，就顺带过期时间也用redis处理了。
        String name = user.getNickName();
        String phone = user.getAuthPhoneNumber();
        Map<String, Object> payloadMap = new HashMap<>();
        payloadMap.put(HService.attr_login_type, HService.type_user);
        payloadMap.put(HService.attr_login_id, userId);
//        payloadMap.put(HService.attr_login_name, name);
//        payloadMap.put(HService.attr_login_phone, phone);

        String openId = user.getOpenId();
        String sessionKey = user.getSessionKey();
        String unionId = user.getUnionId();

        payloadMap.put(HService.attr_login_channel_open_id, openId);
        payloadMap.put(HService.attr_login_channel_session_key, sessionKey);
        payloadMap.put(HService.attr_login_channel_unionid, unionId);

        String token = null;
        try {
            token = TokenUtils.creatTokenHS256(payloadMap, redis, TOKEN_EXPIRE_TIME);
        } catch (JOSEException e) {
            log.error("token生成失败");
            log.error(e.getMessage(), e);
        }
        setTokenByIdRedis(userId, token);
        return token;
    }


    /**
     * 设置token到redis
     *
     * @param id
     * @param token
     */
    public void setTokenByIdRedis(long id, String token) {
        String key = formatTokenByIdKey(id);
        redis.setString(key, token, TimeUtils.DAY_15);
    }

    /**
     * 从redis里获取token
     *
     * @param id
     * @return
     */
    public String getTokenByIdRedis(long id) {
        String key = formatTokenByIdKey(id);
        return redis.getString(key);
    }

    public void removeTokenByIdRedis(long id) {
        String key = formatTokenByIdKey(id);
        redis.remove(key);
    }

    /**
     * 获取用户信息，并且更新到数据库
     *
     * @param sessionKey
     * @param signature
     * @param rawData
     * @param encryptedData
     * @param iv
     * @param userId
     * @return
     */
    public UserEntity getWxUserInfo(String sessionKey, String signature, String rawData, String encryptedData, String iv, Long userId) {
        // 用户信息校验
        if (!wxMaService.getUserService().checkUserInfo(sessionKey, rawData, signature)) {
            WxMaConfigHolder.remove();//清理ThreadLocal
            AssertUtils.newThrow("用户信息校验失败");
        }

        // 解密用户信息
        WxMaUserInfo userInfo = wxMaService.getUserService().getUserInfo(sessionKey, encryptedData, iv);
        WxMaConfigHolder.remove();//清理ThreadLocal

        String nickName = userInfo.getNickName();
        String avatarUrl = userInfo.getAvatarUrl();

        UserEntity user = get(userId);

        if (user == null) {
            log.error("用户不存在 userId={}", userId);
            AssertUtils.newThrow(HErrorCode.USER_NOT_EXIST, "用户不存在");
        }
        user.setNickName(nickName);
        user.setAvatarUrl(avatarUrl);

        boolean is = update(user);
        AssertUtils.isFalse(is, "更新用户失败");
        return user;
    }


    /**
     * 获取用户手机号，并且更新到数据库
     *
     * @param code
     * @return
     */
    public UserEntity getPhoneInfo(Long userId, String code) {
        UserEntity user = get(userId);

        if (user == null) {
            log.error("用户不存在 userId={}", userId);
            AssertUtils.newThrow(HErrorCode.USER_NOT_EXIST, "用户不存在");
        }
        // 解密手机号
        try {
            WxMaPhoneNumberInfo phoneNoInfo = wxMaService.getUserService().getPhoneNoInfo(code);
            if (phoneNoInfo != null) {
                user.setAuthPhoneNumber(phoneNoInfo.getPhoneNumber());
            }
        } catch (WxErrorException e) {
            log.error("解密手机号失败 code={}", code);
            AssertUtils.newThrow("解密手机号失败");
        }
        WxMaConfigHolder.remove();//清理ThreadLocal

        boolean is = update(user);
        AssertUtils.isFalse(is, "更新用户失败");
        return user;
    }
}

